package uk.ac.glasgow.demosocs.impl.election;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

import uk.ac.glasgow.demosocs.*;
import uk.ac.glasgow.demosocs.users.*;

/**
 * implementation of election interface
 * 
 * @author Administrator
 * 
 */
public class ElectionImpl implements Election, Serializable {

	// election id is the unique identifier for election
	private String mElectionID;
	private String mElectionName;

	// election type in string
	private String mElectionType;
	private ElectionState mElectionState;

	// result of this particular election
	private ElectionResultImpl mElectionResult;

	// nomination open time - data type
	private Date mNominationOpenDate;
	// voting start time - data type
	private Date mElectionStartDate;
	// election end time - data type
	private Date mElectionEndDate;

	// electorate for the election
	private Electorate mElectorate;

	// collection of nominee
	private Set<Nominee> mNomineeSet;

	// collection of candidates
	private Set<CandidateImpl> mCandidateSet;


	/**
	 * constructor
	 * 
	 * @param name
	 *            string election name
	 * @param type
	 *            string election type
	 * @param electionID
	 *            id for the election
	 */
	public ElectionImpl(String name, String type) {
		mElectionName = name;
		mElectionType = type;

		mElectionState = ElectionState.NOT_STARTED;
		// initial setups

		mNomineeSet = new HashSet<Nominee>();
		mCandidateSet = new HashSet<CandidateImpl>();

		mNominationOpenDate = null;
		mElectionStartDate = null;
		mElectionEndDate = null;

		// pass this election to the result
		mElectionResult = new ElectionResultImpl(this);

		mElectorate = null;
		
	}

	public void setElectionID(String EID) {
		mElectionID = EID;
	}

	/**
	 * return the election ID
	 * 
	 * @Override method gete
	 */
	public String getEID() {

		return mElectionID;
	}

	/**
	 * get election name
	 * 
	 * @return electionName
	 */
	public String getElectionName() {
		return mElectionName;
	}

	/**
	 * type of the election, will define later
	 * 
	 * @return string of election type
	 */
	public String getElectionType() {
		return mElectionType;
	}

	/**
	 * the result of the election return ElectionResult object
	 */
	public ElectionResult getElectionResult() {
		checkTime();
		//check if the election ended
		if(mElectionState.equals(ElectionState.ENDED))
			return mElectionResult;
		else{
			System.err.println("The election has not ended!");
			return mElectionResult;
		}
	}

	/**
	 * get the election state return ElectionState object
	 */
	public ElectionState getElectionState() {
		return mElectionState;
	}

	@Override
	/**
	 * returns a set of candidate for the election
	 */
	public Set<Candidate> getCandidates() {

		// process of changing the collection of CandidateImple to a collection
		// of Candidate
		Set<Candidate> returnCandidates = new HashSet<Candidate>();
		for (Candidate can : mCandidateSet) {
			returnCandidates.add(can);
		}

		return returnCandidates;
	}

	/**
	 * get the candidateimpl collection
	 * 
	 * @return set<candidateimpl>
	 */
	protected Set<CandidateImpl> getCandidateImplSet() {
		return mCandidateSet;
	}

	@Override
	/**
	 * get open nomination
	 * return open nomination data
	 */
	public Date getOpenNominations() {

		return mNominationOpenDate;
	}

	/**
	 * set the nomination open date
	 * 
	 * @param mNominationOpenDate
	 *            date
	 */
	public void setNominationOpenDate(Date mNominationOpenDate) {
		this.mNominationOpenDate = mNominationOpenDate;
	}

	public void setStart(Date start) {
		mElectionStartDate = start;
	}

	@Override
	public Date getStart() {
		return mElectionStartDate;
	}

	public void setEnd(Date end) {
		mElectionEndDate = end;
	}

	@Override
	public Date getEnd() {
		return mElectionEndDate;
	}

	public Electorate getElectorate() {
		return mElectorate;
	}

	public void setElectorate(Electorate ele) {
		mElectorate = ele;
	}

	public Set<Nominee> getNominees() {
		return mNomineeSet;
	}

	/**
	 * change the election statue to Nomination_open
	 */
	public void openNomination() {
		mElectionState = ElectionState.NOMINATIONS_OPEN;
	}

	public void openElection() {
		mElectionState = ElectionState.STARTED;
	}

	/**
	 * finish the election
	 */
	public void endElection() {
		mElectionState = ElectionState.ENDED;
	}

	/**
	 * nominate process
	 * 
	 * @param nominatorID
	 *            - id for who do the nominate
	 * @param nomineeID
	 *            - id for who been nominated
	 */
	public void nominate(String nominatorID, String nomineeID) {
		Voter nominetor = mElectorate.getVoterByID(nominatorID);
		
		//check time and change election state
		checkTime();
		
		// check whether the nomination has opened
		if (mElectionState == ElectionState.NOMINATIONS_OPEN) {
			// the nominator shoule be in the electorate collection
			if (mElectorate.containsGUID(nominatorID)) {
				// the nominee should also be in the electorate collection
				if (mElectorate.containsGUID(nomineeID)) {
					// do nominate
					nominetor.castNominate();
					// add the nominee to the list
					Voter nominated = mElectorate.getVoterByID(nomineeID);
					// for every voted create a new object of nominee and store
					// it in the nominee collection
					mNomineeSet.add(new Nominee(nominated.getSurname(),
							nominated.getForename(), nominated.getUserGUID(),
							nominated.getPassword()));

					// change the nominee role to "Nominee"
					changeToRoleByID(nomineeID, Voter.NOMINEE);
				} else {
					System.err
							.println("The nominee does not in the election electorate domian.");
				}
			} else {
				System.err
						.println("The nominator does not in the list or has been nominated.");
			}
		} else {
			System.err.println("The nominations for the current election are not open");
		}
	}

	/**
	 * voting process
	 * 
	 * @param voterGUID
	 *            - id
	 * @param VotedGUID
	 *            - id
	 */
	public void vote(String voterGUID, String VotedGUID) {
		Voter voter = mElectorate.getVoterByID(voterGUID);
		//check time
		checkTime();
		
		// check election state
		if (mElectionState == ElectionState.STARTED) {

			// check voter
			if (mElectorate.containsGUID(voterGUID)) {

				// check voted candidate
				if (isCandidateSetComtainsGUID(VotedGUID)) {
					voter.castVote(getCandidateByGUID(VotedGUID));
				} else
					System.err.println("The candidate is not in the election.");

			} else
				System.err.println("The voter is not valid for the election.");
		} else
			System.err.println("Voting status is not currently active");

	}

	/**
	 * notify all nominee in the nominee collection
	 */
	public void notifyNominee() {
		// notify
		for (Nominee nominee : mNomineeSet) {
			nominee.notifyTheNominee();
		}
	}

	public void nomineeAccept(String guid) {
		// check the nominee
		if (isNomineeSetComtainsGUID(guid)) {
			Nominee nominee = getNomineeByGUID(guid);
			// create a new candidate based on the nominee and store in the
			// candidate collection
			mCandidateSet.add(new CandidateImpl(nominee.getSurname(), nominee
					.getForename(), nominee.getUserGUID(), nominee.getPassword()));

			// remove the nominee from the nominee set
			mNomineeSet.remove(nominee);

			// change the nominee's role to candidate
			changeToRoleByID(guid, Voter.CANDIDATE);
		} else
			System.err.println("Not in the nominee list.");
	}

	public void nomineeDecline(String guid) {
		// check the nominee
		if (isNomineeSetComtainsGUID(guid)) {
			// remove nominee from nominee list
			mNomineeSet.remove(getNomineeByGUID(guid));

			// change role to voter
			changeToRoleByID(guid, Voter.VOTER);
		}
	}

	/**
	 * 
	 * @param id
	 *            - guid to check
	 * @return true if candidate collection contains the guid
	 */
	public boolean isCandidateSetComtainsGUID(String id) {
		boolean contain = false;
		for (User user : mCandidateSet) {
			if (user.getUserGUID().equals(id))
				contain = true;
		}

		return contain;
	}

	/**
	 * 
	 * @param id
	 *            - guid to check
	 * @return true if nominee collection contains the guid
	 */
	public boolean isNomineeSetComtainsGUID(String id) {
		boolean contain = false;
		for (User user : mNomineeSet) {
			if (user.getUserGUID().equals(id))
				contain = true;
		}

		return contain;
	}

	/**
	 * get the voter in the electorate by guid
	 * @param guid - string
	 * @return voter object
	 */
	public Voter getVoterByGUID(String guid){
		return mElectorate.getVoterByID(guid);
	}
	
	/**
	 * get the candidateimpl object
	 * 
	 * @param id
	 *            - guid
	 * @return candidateimpl object
	 */
	public CandidateImpl getCandidateByGUID(String id) {

		for (CandidateImpl can : mCandidateSet) {
			if (can.getGUID().equals(id))
				return can;
		}
		return null;
	}

	/**
	 * get the nominee object
	 * 
	 * @param id
	 *            - guid
	 * @return nominee object
	 */
	public Nominee getNomineeByGUID(String id) {

		for (Nominee nominee : mNomineeSet) {
			if (nominee.getUserGUID().equals(id))
				return nominee;
		}
		return null;
	}

	/**
	 * set the guid to a specific role in all collections in the election
	 * 
	 * @param guid
	 * @param role
	 *            String supposed be Voter, Candidate, Admin, Nominee
	 */
	private void changeToRoleByID(String guid, String role) {
		// search and change role in electorate
		if (mElectorate.containsGUID(guid)) {
			for (Voter voter : mElectorate.getVoterCollection()) {
				if (voter.getUserGUID().equals(guid))
					voter.setRole(role);
			}
		}

		// nominee set
		if (isNomineeSetComtainsGUID(guid)) {
			for (Nominee nominee : mNomineeSet) {
				if (nominee.getUserGUID().endsWith(guid))
					nominee.setRole(role);
			}
		}

		// candidate set
		if (isCandidateSetComtainsGUID(guid)) {
			for (CandidateImpl can : mCandidateSet) {
				if (can.getGUID().equals(guid))
					can.setRole(role);
			}
		}
	}
	
	/**
	 * check time and change the election status
	 * 
	 */
	public void checkTime(){
		Date mNow = new Date();
		//before nomination open is not started
		if(mNow.before(mNominationOpenDate)){
			mElectionState = ElectionState.NOT_STARTED;

		} else if(mNow.before(mElectionStartDate)){
			//before start is nomination
			openNomination();

		}
		else if(mNow.before(mElectionEndDate)){
			//before end is started
			openElection();

		}
		else if(mNow.after(mElectionEndDate)){
			//before published is election end
			endElection();
		}
	}
}
